<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>The source code</title>
  <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
  <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
  <style type="text/css">
    .highlight { display: block; background-color: #ddd; }
  </style>
  <script type="text/javascript">
    function highlight() {
      document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
    }
  </script>
</head>
<body onload="prettyPrint(); highlight();">
  <pre class="prettyprint lang-js"><span id='global-property-'>/**
</span> * @ignore
 * abstraction of tree node ,root and other node will extend it
 * @author yiminghe@gmail.com
 */
KISSY.add(&quot;tree/node&quot;, function (S, Node, Container, TreeNodeRender) {
    var $ = Node.all,
        KeyCode = Node.KeyCode;

<span id='KISSY-Tree-Node'>    /**
</span>     * Tree Node. xclass: 'tree-node'.
     * @class KISSY.Tree.Node
     * @extends KISSY.Component.Container
     */
   return Container.extend({
        bindUI: function () {
            this.on('afterAddChild', onAddChild);
            this.on('afterRemoveChild', onRemoveChild);
            this.on('afterAddChild afterRemoveChild', syncAriaSetSize);
        },

        syncUI: function () {
            // 集中设置样式
            refreshCss(this);
            syncAriaSetSize.call(this, {
                target: this
            });
        },

        handleKeyDownInternal: function (e) {
            var self = this,
                processed = true,
                tree = self.get(&quot;tree&quot;),
                expanded = self.get(&quot;expanded&quot;),
                nodeToBeSelected,
                isLeaf = self.get(&quot;isLeaf&quot;),
                children = self.get(&quot;children&quot;),
                keyCode = e.keyCode;

            // 顺序统统为前序遍历顺序
            switch (keyCode) {
                case KeyCode.ENTER:
                    return self.handleClickInternal(e);
                    break;

                // home
                // 移到树的顶层节点
                case KeyCode.HOME:
                    nodeToBeSelected = tree;
                    break;

                // end
                // 移到最后一个可视节点
                case KeyCode.END:
                    nodeToBeSelected = getLastVisibleDescendant(tree);
                    break;

                // 上
                // 当前节点的上一个兄弟节点的最后一个可显示节点
                case KeyCode.UP:
                    nodeToBeSelected = getPreviousVisibleNode(self);
                    break;

                // 下
                // 当前节点的下一个可显示节点
                case KeyCode.DOWN:
                    nodeToBeSelected = getNextVisibleNode(self);
                    break;

                // 左
                // 选择父节点或 collapse 当前节点
                case KeyCode.LEFT:
                    if (expanded &amp;&amp; (children.length || isLeaf === false)) {
                        self.set(&quot;expanded&quot;, false);
                    } else {
                        nodeToBeSelected = self.get('parent');
                    }
                    break;

                // 右
                // expand 当前节点
                case KeyCode.RIGHT:
                    if (children.length || isLeaf === false) {
                        if (!expanded) {
                            self.set(&quot;expanded&quot;, true);
                        } else {
                            nodeToBeSelected = children[0];
                        }
                    }
                    break;

                default:
                    processed = false;
                    break;
            }

            if (nodeToBeSelected) {
                nodeToBeSelected.select();
            }

            return processed;
        },

        next: function () {
            var self = this,
                parent = self.get('parent'),
                siblings,
                index;
            if (!parent) {
                return null;
            }
            siblings = parent.get('children');
            index = S.indexOf(self, siblings);
            if (index == siblings.length - 1) {
                return null;
            }
            return siblings[index + 1];
        },

        prev: function () {
            var self = this,
                parent = self.get('parent'),
                siblings,
                index;
            if (!parent) {
                return null;
            }
            siblings = parent.get('children');
            index = S.indexOf(self, siblings);
            if (index === 0) {
                return null;
            }
            return siblings[index - 1];
        },

<span id='KISSY-Tree-Node-method-select'>        /**
</span>         * Select current tree node.
         */
        select: function () {
            this.set('selected', true);
        },

        handleClickInternal: function (e) {
            var self = this,
                target = $(e.target),
                expanded = self.get(&quot;expanded&quot;),
                tree = self.get(&quot;tree&quot;);
            tree.focus();
            if (target.equals(self.get(&quot;expandIconEl&quot;))) {
                self.set(&quot;expanded&quot;, !expanded);
            } else {
                self.select();
                self.fire(&quot;click&quot;);
            }
            return true;
        },

<span id='KISSY-Tree-Node-method-createChildren'>        /**
</span>         * override root 's renderChildren to apply depth and css recursively
         */
        createChildren: function () {
            var self = this;
           self.renderChildren.apply(self, arguments);
            // only sync child sub tree at root node
            if (self === self.get('tree')) {
                updateSubTreeStatus(self, self, -1, 0);
            }
        },

        _onSetExpanded: function (v) {
            var self = this;
            refreshCss(self);
            self.fire(v ? &quot;expand&quot; : &quot;collapse&quot;);
        },

        _onSetSelected: function (v, e) {
            var tree = this.get(&quot;tree&quot;);
            if (e &amp;&amp; e.byPassSetTreeSelectedItem) {
            } else {
                tree.set('selectedItem', v ? this : null);
            }
        },

<span id='KISSY-Tree-Node-method-expandAll'>        /**
</span>         * Expand all descend nodes of current node
         */
        expandAll: function () {
            var self = this;
            self.set(&quot;expanded&quot;, true);
            S.each(self.get(&quot;children&quot;), function (c) {
                c.expandAll();
            });
        },

<span id='KISSY-Tree-Node-method-collapseAll'>        /**
</span>         * Collapse all descend nodes of current node
         */
        collapseAll: function () {
            var self = this;
            self.set(&quot;expanded&quot;, false);
            S.each(self.get(&quot;children&quot;), function (c) {
                c.collapseAll();
            });
        }
    }, {
        ATTRS: {
            xrender: {
                value: TreeNodeRender
            },

            checkable: {
                value: false,
                view: 1
            },

            // 事件代理
            handleMouseEvents: {
                value: false
            },

<span id='KISSY-Tree-Node-property-isLeaf'>            /**
</span>             * Only For Config.
             * Whether to force current tree node as a leaf.                 *
             * It will change as children are added.
             *
             * @type {Boolean}
             */
            isLeaf: {
                view: 1
            },

<span id='KISSY-Tree-Node-property-expandIconEl'>            /**
</span>             * Element for expand icon.
             * @type {KISSY.NodeList}
             */
            expandIconEl: {
            },

<span id='KISSY-Tree-Node-property-iconEl'>            /**
</span>             * Element for icon.
             * @type {KISSY.NodeList}
             */
            iconEl: {
            },

<span id='KISSY-Tree-Node-property-selected'>            /**
</span>             * Whether current tree node is selected.
             * @type {Boolean}
             */
            selected: {
                view: 1
            },

<span id='KISSY-Tree-Node-property-expanded'>            /**
</span>             * Whether current tree node is expanded.
             * @type {Boolean}
             * Defaults to: false.
             */
            expanded: {
                sync: 0,
                value: false,
                view: 1
            },

<span id='KISSY-Tree-Node-property-tooltip'>            /**
</span>             * html title for current tree node.
             * @type {String}
             */
            tooltip: {
                view: 1
            },

<span id='KISSY-Tree-Node-property-tree'>            /**
</span>             * Tree instance current tree node belongs to.
             * @type {KISSY.Tree}
             */
            tree: {
                getter: function () {
                    var from = this;
                    while (from &amp;&amp; !from.isTree) {
                        from = from.get('parent');
                    }
                    return from;
                }
            },

<span id='KISSY-Tree-Node-property-depth'>            /**
</span>             * depth of node.
             * @type {Number}
             */
            depth: {
                view: 1
            },

            focusable: {
                value: false
            },

            defaultChildCfg: {
                value: {
                    xclass: 'tree-node'
                }
            }
        },
        xclass: 'tree-node'
    });

    // # ------------------- private start

    function onAddChild(e) {
        var self = this;
        if (e.target == self) {
            updateSubTreeStatus(self, e.component, self.get('depth'), e.index);
        }
    }

    function onRemoveChild(e) {
        var self = this;
        if (e.target == self) {
            recursiveSetDepth(self.get('tree'), e.component);
            refreshCssForSelfAndChildren(self, e.index);
        }
    }

    function syncAriaSetSize(e) {
        var self = this;
        if (e.target === self) {
            self.el.setAttribute('aria-setsize',
                self.get('children').length);
        }
    }

    function isNodeSingleOrLast(self) {
        var parent = self.get('parent'),
            children = parent &amp;&amp; parent.get(&quot;children&quot;),
            lastChild = children &amp;&amp; children[children.length - 1];
        // 根节点
        // or
        // 父亲的最后一个子节点
        return !lastChild || lastChild == self;
    }

    function isNodeLeaf(self) {
        var isLeaf = self.get(&quot;isLeaf&quot;);
        // 强制指定了 isLeaf，否则根据儿子节点集合自动判断
        return !(isLeaf === false || (isLeaf === undefined &amp;&amp; self.get(&quot;children&quot;).length));
    }

    function getLastVisibleDescendant(self) {
        var children = self.get(&quot;children&quot;);
        // 没有展开或者根本没有儿子节点，可视的只有自己
        if (!self.get(&quot;expanded&quot;) || !children.length) {
            return self;
        }
        // 可视的最后一个子孙
        return getLastVisibleDescendant(children[children.length - 1]);
    }

    // not same with _4e_previousSourceNode in editor !
    function getPreviousVisibleNode(self) {
        var prev = self.prev();
        if (!prev) {
            prev = self.get('parent');
        } else {
            prev = getLastVisibleDescendant(prev);
        }
        return prev;
    }

    // similar to _4e_nextSourceNode in editor
    function getNextVisibleNode(self) {
        var children = self.get(&quot;children&quot;),
            n,
            parent;
        if (self.get(&quot;expanded&quot;) &amp;&amp; children.length) {
            return children[0];
        }
        // 没有展开或者根本没有儿子节点
        // 深度遍历的下一个
        n = self.next();
        parent = self;
        while (!n &amp;&amp; (parent = parent.get('parent'))) {
            n = parent.next();
        }
        return n;
    }

    /*
     每次添加/删除节点，都检查自己以及自己子孙 class
     每次 expand/collapse，都检查
     */
    function refreshCss(self) {
        if (self.get &amp;&amp; self.view) {
            self.view.refreshCss(isNodeSingleOrLast(self), isNodeLeaf(self));
        }
    }

    function updateSubTreeStatus(self, c, depth, index) {
        var tree = self.get(&quot;tree&quot;);
        if (tree) {
            recursiveSetDepth(tree, c, depth + 1);
            refreshCssForSelfAndChildren(self, index);
        }
    }

    function recursiveSetDepth(tree, c, setDepth) {
        if (setDepth !== undefined) {
            c.set(&quot;depth&quot;, setDepth);
        }
        S.each(c.get(&quot;children&quot;), function (child) {
            if (typeof setDepth == 'number') {
                recursiveSetDepth(tree, child, setDepth + 1);
            } else {
                recursiveSetDepth(tree, child);
            }
        });
    }

    function refreshCssForSelfAndChildren(self, index) {
        refreshCss(self);
        index = Math.max(0, index - 1);
        var children = self.get('children'),
            c,
            len = children.length;
        for (; index &lt; len; index++) {
            c = children[index];
            refreshCss(c);
            c.el.setAttribute(&quot;aria-posinset&quot;, index + 1);
        }
    }

    // # ------------------- private end
}, {
    requires: ['node', 'component/container', './node-render']
});

<span id='global-property-'>/**
</span> * @ignore
 * 2012-09-25
 *  - 去除 dblclick 支持，该交互会重复触发 click 事件，可能会重复执行逻辑
 */</pre>
</body>
</html>
